using System;

namespace Matrix
{
	/// <summary>
	/// Represents a matrix.
	/// </summary>
	public class Matrix
	{
	    /// <summary>
	    /// Gives access to data in the matrix.
	    /// </summary>
	    public double[,] Data { get; private set; }

	    /// <summary>
	    /// Gets number of rows.
	    /// </summary>
	    public int Rows { get; private set; }

	    /// <summary>
	    /// Gets number of columns.
	    /// </summary>
	    public int Cols { get; private set; }

	    /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="nrows">Number of rows</param>
        /// <param name="ncols">Number of columns</param>
		public Matrix(int nrows,int ncols)
		{
			Rows = nrows;
			Cols = ncols;
			Data = new double[Rows,Cols];
		}

        /// <summary>
        /// Makes a copy of specified matrix.
        /// </summary>
        /// <param name="mat">Matrix to be copied</param>
		public Matrix(Matrix mat)
		{
			Cols = mat.Cols;
			Rows = mat.Rows;
			Data = new double[Rows,Cols];

			for(int i=0; i<Rows; i++)
			{
				for(int j=0; j<Cols;j++)
				{
					Data[i,j] = mat.Data[i,j];
				}
			}			
		}

        /// <summary>
        /// Multiplication operator for matrices.
        /// </summary>
        /// <param name="a">operand a</param>
        /// <param name="b">operand b</param>
        /// <returns>a * b</returns>
		public static Matrix operator*(Matrix a,Matrix b)
		{
            if (a.Cols != b.Rows)
            {
                throw new Exception("Wrong Matrix dimensions");
            }

			Matrix result = new Matrix(a.Rows,b.Cols);

			for(int i=0; i<result.Rows; i++)
			{
				for(int j=0; j<result.Cols;j++)
				{
					result.Data[i,j] = 0;
					for(int iter=0; iter <result.Cols; iter++)
					{
						result.Data[i,j] += a.Data[i,iter]*b.Data[iter,j];
					}
				}
			}

			return result;
		}

        /// <summary>
        /// Multiplication operator for matrix and a scalar.
        /// </summary>
        /// <param name="a">matrix</param>
        /// <param name="b">scalar</param>
        /// <returns>a * b</returns>
		public static Matrix operator*(Matrix a,double b)
		{
			Matrix result = new Matrix(a.Rows,a.Cols);

			for(int i=0; i<result.Rows; i++)
			{
				for(int j=0; j<result.Cols;j++)
				{
					result.Data[i,j] = a.Data[i,j]*b;
				}
			}

			return result;
		}

        /// <summary>
        /// Sum operator for matrices.
        /// </summary>
        /// <param name="a">operand a</param>
        /// <param name="b">operand b</param>
        /// <returns>a + b</returns>
		public static Matrix operator+(Matrix a,Matrix b)
		{
            if (a.Cols != b.Cols || a.Rows != b.Rows)
            {
                throw new Exception("Matrices must be same sizes");
            }

			Matrix result = new Matrix(a.Rows,a.Cols);

			for(int i=0; i<result.Rows; i++)
			{
				for(int j=0; j<result.Cols;j++)
				{
					result.Data[i,j] = a.Data[i,j]+b.Data[i,j];
				}
			}

			return result;
		}

        /// <summary>
        /// Subtraction operator for matrices.
        /// </summary>
        /// <param name="a">operand a</param>
        /// <param name="b">operand b</param>
        /// <returns>a - b</returns>
		public static Matrix operator-(Matrix a,Matrix b)
		{
			if (a.Cols != b.Cols || a.Rows != b.Rows) throw new Exception("Matrices must be same sizes");
			
            Matrix result = new Matrix(a.Rows,a.Cols);
			
            for(int i=0; i<result.Rows; i++)
			{
				for(int j=0; j<result.Cols;j++)
				{
					result.Data[i,j] = a.Data[i,j]-b.Data[i,j];
				}
			}

			return result;
		}

        /// <summary>
        /// Gets a minor of the matrix. A minor is a matrix that is created from another matrix 
        /// by removing one especified row and one specified column.
        /// </summary>
        /// <param name="x">Row to be deleted</param>
        /// <param name="y">Column to be deleted</param>
        /// <returns>Minor</returns>
		public Matrix GetMinor(int x,int y)
		{
			Matrix result = new Matrix(Rows-1,Cols-1);

            int docx = 0;
            int docy = 0;
            int tx = 0;
            int ty = 0;

			while(tx<Rows)
			{
				docy=0;
				ty = 0;
				
                if(tx!=x)
				{
					while(ty<Cols)
					{
						if(ty!=y)
						{
							result.Data[docx,docy] = Data[tx,ty];
							docy++;
						}
						ty++;
					}
					docx++;
				}

				tx++;
			}

			return result;
		}

        /// <summary>
        /// Returns transposed matrix.
        /// </summary>
        /// <returns>Transposed matrix</returns>
		public Matrix GetTransposed()
		{
			Matrix result = new Matrix(Cols,Rows);
            
            for (int i = 0; i < Rows; i++)
            {
                for (int j = 0; j < Cols; j++)
                {
                    result.Data[j, i] = Data[i, j];
                }
            }

			return result;
		}

        /// <summary>
        /// Calculates determinant.
        /// </summary>
        /// <returns>Determinant</returns>
		public double Det()
		{
            if (Cols != Rows) 
            {
                throw new Exception("Matrix must be square"); 
            }

            if (Cols == 1 && Rows == 1)
            {
                return Data[0, 0];
            }

            int row = 0;
			int col;
			double result = 0;

			for(col = 0; col<Cols; col++)
			{
				result += Data[row,col]*Math.Pow(-1,row+col+2)*this.GetMinor(row,col).Det();
			}

			return result;
		}

        /// <summary>
        /// Returns inversed matrix.
        /// </summary>
        /// <returns>Inversed matrix.</returns>
		public Matrix GetInversed()
		{
			if(Rows!=Cols) throw new Exception("Matrix must be square");

			Matrix result = new Matrix(Rows, Cols);
			double d = Det();

            for (int i = 0; i < Rows; i++)
            {
                for (int j = 0; j < Cols; j++)
                {
                    result.Data[i, j] = Math.Pow(-1, i + j) * this.GetMinor(i, j).Det();
                }
            }

			result = result.GetTransposed();
			result = result * (1/d);

			return result;
		}

        /// <summary>
        /// Returns the resulting matrix from Gauss elimination procedure. 
        /// Matrix must be n+1 x n and must be a describtion of a linear system.
        /// </summary>
        /// <returns></returns>
		public Matrix Gauss()
		{
			Matrix result = new Matrix(Rows,1);
			Matrix copy = new Matrix(this);

			for(int a=0; a<copy.Rows ; a++)
			{
				if(copy.Data[a,a] == 0)
				{
					double temp;

                    for (int k = copy.Rows - 1; k > a; k--)
                    {
                        if (copy.Data[k, a] != 0)
                        {
                            for (int l = 0; l < copy.Cols; l++)
                            {
                                temp = copy.Data[a, l];
                                copy.Data[a, l] = copy.Data[k, l];
                                copy.Data[k, l] = temp;
                            }
                            k = a - 1;
                        }
                    }
				}

                for (int m = a; m < copy.Rows; m++)
                {
                    if (copy.Data[m, a] != 0)
                    {
                        for (int n = copy.Cols - 1; n >= 0; n--)
                        {
                            copy.Data[m, n] = copy.Data[m, n] / copy.Data[m, a];
                        }
                    }
                }

                for (int k = a + 1; k < copy.Rows; k++)
                {
                    if (copy.Data[k, a] != 0)
                    {
                        for (int l = a; l < copy.Cols; l++)
                        {
                            copy.Data[k, l] = copy.Data[k, l] - copy.Data[a, l];
                        }
                    }
                }
			}

			for(int k = copy.Rows-1; k>=0; k--)
			{
				result.Data[k,0] = copy.Data[k,Cols-1];

                for (int l = k + 1; l < copy.Cols - 1; l++)
                {
                    result.Data[k, 0] -= copy.Data[k, l] * result.Data[l, 0];
                }
			}

			return result;
		}
	}
}
